﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using System.Xml;

namespace Catsland.Core
{
	public class CatsBoundingBox
	{
		public List<CatsBoundingBox> m_subBounding;
		public CatsBoundingBox m_parentBounding;

		public Vector2[] m_convex;
		public Vector2 m_heightRange;

		public bool m_isRectangle = false;
		public Vector2 m_XBound;
		public Vector2 m_YBound;

		public CatsBoundingBox()
		{
			m_XBound = new Vector2(-0.1f, 0.1f);
			m_YBound = new Vector2(-0.1f, 0.1f);
			m_heightRange = new Vector2(0.0f, 0.1f);
			m_isRectangle = false;
		}

		public void UpdateBoundFromRectangle()
		{
			m_convex = new Vector2[4];

			m_convex[0] = new Vector2(m_XBound.X, m_YBound.Y);
			m_convex[1] = new Vector2(m_XBound.Y, m_YBound.Y);
			m_convex[2] = new Vector2(m_XBound.Y, m_YBound.X);
			m_convex[3] = new Vector2(m_XBound.X, m_YBound.X);

			m_isRectangle = true;
		}

		public void AddChildBoundingBox(CatsBoundingBox boundingBox)
		{
			m_subBounding.Add(boundingBox);
		}

		// boundingBox has been added offset
		public bool IsCollide(CatsBoundingBox boundingBox, 
			Vector2 XYOffset, float ZOffset,
			Vector2 iXYOffset, float iZOffset)
		{
			// Judge Collide on Z
			Vector2 iZRange = new Vector2(iZOffset, iZOffset) + m_heightRange;
			Vector2 ZRange = new Vector2(ZOffset, ZOffset) + boundingBox.m_heightRange;

			if (iZRange.X > ZRange.Y || iZRange.Y < ZRange.X)
			{
				return false;
			}

			// Judge Collide on XYPlane
			// potential separation axes. they get converted into push 
			List<Vector2> axis = new List<Vector2>();

			// max of 16 vertices per polygon 
			int i, j;
			for(j = m_convex.Length - 1, i = 0;
				i < m_convex.Length; j = i, i++)
			{
				Vector2 edge = m_convex[i] - m_convex[j];
				Vector2 normal = new Vector2(-edge.Y, edge.X);
				axis.Add(normal);

				if(AxisSeparatePolygons(normal, m_convex, boundingBox.m_convex,
					iXYOffset, iZOffset, XYOffset, ZOffset))
				{
					return false;
				}
			}

			for(j = boundingBox.m_convex.Length - 1, i = 0;
				i < boundingBox.m_convex.Length; j = i, i++)
			{
				Vector2 edge = boundingBox.m_convex[i] - boundingBox.m_convex[j];
				Vector2 normal = new Vector2(-edge.Y, edge.X);
				axis.Add(normal);

				if(AxisSeparatePolygons(normal, m_convex, boundingBox.m_convex,
					iXYOffset, iZOffset, XYOffset, ZOffset))
				{
					return false;
				}
			}

			/*
			// find the MTD among all the separation vectors 
			MTD = FindMTD(Axis, iNumAxis); 

			// makes sure the push vector is pushing A away from B 
			Vector D = A.Position – B.Position; 
			if (D dot MTD < 0.0f) 
				MTD = -MTD; 
			*/
			// this level collide, check sub boundingbox
			if (m_subBounding != null && m_subBounding.Count > 0)
			{
				foreach (CatsBoundingBox bounding in m_subBounding)
				{
					bool result = bounding.IsCollide(boundingBox,
						XYOffset, ZOffset,
						iXYOffset, iZOffset);
					if (result == true)
					{
						return true;
					}
				}
				return false;
			}
			else
			{
				return true;
			}
		}

		bool AxisSeparatePolygons(Vector2 Axis, Vector2 []A, Vector2 []B, 
			Vector2 AXYOffset, float AZOffset,
			Vector2 BXYOffset, float BZOffset) 
		{ 
			float mina, maxa; 
			float minb, maxb; 

			Vector2 MinMax = CalculateInterval(Axis, A, AXYOffset, AZOffset);
			mina = MinMax.X;
			maxa = MinMax.Y;
			MinMax = CalculateInterval(Axis, B, BXYOffset, BZOffset);
			minb = MinMax.X;
			maxb = MinMax.Y;

			if (mina > maxb || minb > maxa)
			{
				return true; 
			}
				
			// find the interval overlap 
			/*
			float d0 = maxa - minb; 
			float d1 = maxb - mina; 
			float depth = (d0 < d1)? d0 : d1; 

			
			// convert the separation axis into a push vector (re-normalise 
			// the axis and multiply by interval overlap) 
			float axis_length_squared = Axis dot Axis; 

			Axis *= depth / axis_length_squared; 
			*/
			return false; 
		}

		
		Vector2 CalculateInterval(Vector2 Axis, Vector2 []P, 
			Vector2 XYOffset, float ZOffset) 
		{ 
			float min, max;
			float d = Vector2.Dot(Axis, P[0] + XYOffset); 
			min = max = d; 
			int i;
			for(i = 0; i < P.Length; ++i) 
			{
				float e = Vector2.Dot(P[i] + XYOffset, Axis); 
				if (e < min) 
				{
					min = e;
				}
				else if (e > max)
				{
					max = e; 
				}
			}
			return new Vector2(min, max);
		}

		public bool SaveToNode(XmlNode node, XmlDocument doc)
		{
			XmlElement boundingBox = doc.CreateElement("BoundingBox");
			node.AppendChild(boundingBox);

			boundingBox.SetAttribute("isRectangle", "" + m_isRectangle);

			XmlElement heightRange = doc.CreateElement("HeightRange");
			boundingBox.AppendChild(heightRange);
			heightRange.SetAttribute("min", "" + m_heightRange.X);
			heightRange.SetAttribute("max", "" + m_heightRange.Y);

			if (m_isRectangle)
			{
				XmlElement xBound = doc.CreateElement("XBound");
				boundingBox.AppendChild(xBound);
				xBound.SetAttribute("min", "" + m_XBound.X);
				xBound.SetAttribute("max", "" + m_XBound.Y);

				XmlElement yBound = doc.CreateElement("YBound");
				boundingBox.AppendChild(yBound);
				yBound.SetAttribute("min", "" + m_YBound.X);
				yBound.SetAttribute("max", "" + m_YBound.Y);
			}

			// TODO: add sub boundingBox 
			return true;
		}

		public static CatsBoundingBox LoadFromNode(XmlNode node, Scene scene, GameObject gameObject)
		{
			XmlElement boundingBox = (XmlElement)node;

			bool isRectangle = bool.Parse(boundingBox.GetAttribute("isRectangle"));

			// heightRange
			XmlElement heightRange = (XmlElement)boundingBox.SelectSingleNode("HeightRange");
			Vector2 newHeightRange = new Vector2(float.Parse(heightRange.GetAttribute("min")),
										float.Parse(heightRange.GetAttribute("max")));
			CatsBoundingBox newBoundingBox = new CatsBoundingBox();
			newBoundingBox.m_heightRange = newHeightRange;
			newBoundingBox.m_isRectangle = isRectangle;

			if (isRectangle)
			{
				// XBound
				XmlElement xBound = (XmlElement)boundingBox.SelectSingleNode("XBound");
				newBoundingBox.m_XBound = new Vector2(float.Parse(xBound.GetAttribute("min")),
											float.Parse(xBound.GetAttribute("max")));
				// YBound
				XmlElement yBound = (XmlElement)boundingBox.SelectSingleNode("YBound");
				newBoundingBox.m_YBound = new Vector2(float.Parse(yBound.GetAttribute("min")),
												float.Parse(yBound.GetAttribute("max")));

				newBoundingBox.UpdateBoundFromRectangle();
			}

			// TODO: sub boundingBox

			return newBoundingBox;
		}
	}
}
